home *** CD-ROM | disk | FTP | other *** search
/ Visual Basic Controls / Visual Basic Controls.iso / vbcontrol / axtree / collecti.cls < prev    next >
Encoding:
Visual Basic class definition  |  1998-11-12  |  18.4 KB  |  570 lines

  1. VERSION 1.0 CLASS
  2. BEGIN
  3.   MultiUse = -1  'True
  4. END
  5. Attribute VB_Name = "CollectionEx"
  6. Attribute VB_GlobalNameSpace = False
  7. Attribute VB_Creatable = False
  8. Attribute VB_PredeclaredId = False
  9. Attribute VB_Exposed = False
  10. Option Explicit
  11.  
  12. #Const ValueType = 2    '0=any, 1=non object, 2=objects
  13.                         ' use (2) when possible to save memory
  14.  
  15. Private Declare Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" (dest As Any, source As Any, ByVal bytes As Long)
  16. Private Declare Sub ZeroMemory Lib "kernel32" Alias "RtlZeroMemory" (dest As Any, ByVal numbytes As Long)
  17.  
  18. ' default initial size of the internal array
  19. Const INITSIZE_DEF = 1000
  20. ' default value of the number of items that are allocated when necessary
  21. Const ALLOCATIONCHUNK_DEF = INITSIZE_DEF
  22. ' default fill ratio
  23. Const FILLRATIO_DEF = 2
  24.  
  25. Private Type TValue
  26.     HashCode As Long
  27.     HashIndex As Long
  28.     Key As String
  29. #If ValueType = 2 Then
  30.     Item As Object
  31. #Else
  32.     Item As Variant
  33. #End If
  34. End Type
  35.  
  36. ' initial number of items in the Value() array
  37. Dim m_InitSize As Long
  38. ' number of items that are allocated when necessary
  39. Dim m_AllocationChunk As Long
  40. ' fill ratio - when the ratio between the total number of items
  41. ' in value() and the number of items actually used is greater than
  42. ' this value, additional DATA_CHUNK items are allocated
  43. Dim m_FillRatio As Single
  44.  
  45. ' this array of records holds the values
  46. Private value() As TValue
  47. ' this holds the size of Values()
  48. Private valueSize As Long
  49. ' this array holds the backpointers into values() (or 0 if unused)
  50. Private hashTable() As Long
  51. ' this holds the size of the hash table
  52. Private hashTableSize As Long
  53. ' the number of elements actually used in Value()
  54. Private m_Count As Long
  55. ' true if the collection should be sorted
  56. Private m_Sorted As Boolean
  57. ' this collection holds the values during enumeration loops (see NewEnum)
  58. Private m_values As Collection
  59. ' size of an item in Value()
  60. Dim itemLen As Long
  61. 'highest key
  62. Dim mHighKey As Variant
  63.  
  64. Private Sub Class_Initialize()
  65.     m_InitSize = INITSIZE_DEF
  66.     m_AllocationChunk = ALLOCATIONCHUNK_DEF
  67.     m_FillRatio = FILLRATIO_DEF
  68.     Clear
  69. End Sub
  70.  
  71. ' set allocation values
  72. ' NUMITEMS  is the expected number of items in the collection
  73. '           (the collection can grow above this value)
  74. ' ALLOCATIONCHUCKS is the number of items that must be allocated when necessary
  75. ' FILLRATIO is a number >1 that states how bigger the internal hash table
  76. '           is relative to the number of items (suggested value is 2 or 3)
  77.  
  78. Sub SetMemory(ByVal NumItems As Long, Optional ByVal AllocationChunk As Long, Optional ByVal FillRatio As Single)
  79. Attribute SetMemory.VB_Description = "Allocate initial memory for the items of the collection, sets the allocation unit and the fill ratio for the internal hash table structure"
  80.     ' minimal range checking
  81.     If NumItems < 20 Then NumItems = 20
  82.     If AllocationChunk < 20 Then AllocationChunk = NumItems
  83.     If FillRatio < 1.5 Then FillRatio = 1.5
  84.     ' store into class variables
  85.     m_InitSize = NumItems
  86.     m_AllocationChunk = AllocationChunk
  87.     m_FillRatio = FillRatio
  88.     ' rebuild all internal tables
  89.     If m_Count = 0 Then
  90.         Clear
  91.     Else
  92.         RehashTables NumItems
  93.     End If
  94. End Sub
  95.  
  96. ' destroy all items in the collection
  97.  
  98. Sub Clear()
  99. Attribute Clear.VB_Description = "Remove all items from the collection"
  100.     m_Count = 0
  101.     mHighKey = "0"
  102.     valueSize = m_InitSize
  103.     ReDim value(valueSize) As TValue
  104.     itemLen = Len(value(1))
  105.     ' odd values minimize collisions in the hash table
  106.     hashTableSize = (valueSize * m_FillRatio) Or 1
  107.     ReDim hashTable(hashTableSize) As Long
  108.     ' clear the private collection
  109.     Set m_values = Nothing
  110. End Sub
  111.  
  112. ' return the number of items in the collection
  113.  
  114. Property Get Count() As Long
  115. Attribute Count.VB_Description = "Return the number of items in the collection"
  116.     Count = m_Count
  117. End Property
  118.  
  119. ' add a new item to the collection
  120. ' KEY is not optional (differently from standard collections)
  121. ' if IGNOREIFPRESENT = True, doesn't raise any error if the item is
  122. ' already in the collection
  123. ' BEFORE and AFTER are ignored if the collection is sorted
  124.  
  125. Sub Add(Item As Variant, Key As String, Optional Before As Variant, Optional After As Variant, Optional IgnoreIfPresent As Boolean)
  126. Attribute Add.VB_Description = "Add a new item to the collection; Before and After arguments are ignored if the collection is sorted"
  127.     Dim ndx As Long, hCode As Long, strKey As String
  128.     Dim NewIndex As Long, i As Long
  129.     
  130.     ' check if there is an item with that key
  131.     strKey = Key
  132.     ndx = GetIndex(strKey, hCode)
  133.     ' signal error if the item was already in the collection
  134.     If ndx > 0 Then
  135.         ' raise error, unless the flag is True
  136.         If Not IgnoreIfPresent Then Err.Raise 457
  137.         ' otherwise, just jump to where the item is assigned
  138.         NewIndex = hashTable(ndx)
  139.         GoTo Add_SetItem
  140.     End If
  141.     
  142.     mHighKey = Key
  143.     ' see if we need to allocate more memory
  144.     If m_Count = valueSize Then
  145.         RehashTables valueSize + m_AllocationChunk
  146.         ndx = GetIndex(strKey, hCode)
  147.     End If
  148.     ' now NDX points to the right location in the hashtable
  149.     ndx = -ndx
  150.     
  151.     ' evaluate the newIndex of this item
  152.     If m_Sorted Then
  153.         ' the collection is sorted, so we can use binary search
  154.         NewIndex = -BinarySearch(strKey)
  155.     ElseIf Not IsMissing(Before) Then
  156.         If Not IsMissing(After) Then Err.Raise 5
  157.         If VarType(Before) = vbString Then
  158.             NewIndex = Index(CStr(Before))
  159.         Else
  160.             NewIndex = Before
  161.         End If
  162.         CheckRange NewIndex
  163.     ElseIf Not IsMissing(After) Then
  164.         If VarType(After) = vbString Then
  165.             NewIndex = Index(CStr(After))
  166.         Else
  167.             NewIndex = After
  168.         End If
  169.         ' first check for the range, then increase it
  170.         CheckRange NewIndex
  171.         NewIndex = NewIndex + 1
  172.     Else
  173.         ' both Before and After are omitted, and the collection is not sorted
  174.         NewIndex = m_Count + 1
  175.     End If
  176.  
  177. '    ' evaluate the newIndex of this item
  178. '    If m_Sorted Then
  179. '        ' the collection is sorted, so we can use binary search
  180. '        NewIndex = -BinarySearch(strKey)
  181. '    ElseIf Not IsMissing(Before) Then
  182. '        If Not IsMissing(After) Then Err.Raise 5
  183. '        If VarType(Before) = vbString Then
  184. '            NewIndex = GetIndex((Before), , True) ' pass by value
  185. '        Else
  186. '            NewIndex = Before
  187. '            CheckRange NewIndex
  188. '        End If
  189. '    ElseIf Not IsMissing(After) Then
  190. '        If VarType(After) = vbString Then
  191. '            NewIndex = GetIndex((After), , True) ' pass by value
  192. '        Else
  193. '            NewIndex = After
  194. '            CheckRange NewIndex
  195. '        End If
  196. '    Else
  197. '        ' both Before and After are omitted, and the collection is not sorted
  198. '        NewIndex = m_Count + 1
  199. '    End If
  200.     
  201.     ' we have a new value
  202.     m_Count = m_Count + 1
  203.     ' store the backpointer into the hashtable
  204.     hashTable(ndx) = NewIndex
  205.     
  206.     ' see if we need to make room in the value() array
  207.     If NewIndex <> m_Count Then
  208.         ' make a hole at value(newIndex)
  209.         CopyMemory ByVal VarPtr(value(NewIndex + 1)), ByVal VarPtr(value(NewIndex)), (m_Count - NewIndex) * itemLen
  210.         ZeroMemory ByVal VarPtr(value(NewIndex)), itemLen
  211.         ' adjust backpointers of all subsequent items
  212.         For i = NewIndex + 1 To m_Count
  213.             hashTable(value(i).HashIndex) = i
  214.         Next
  215.     End If
  216.     
  217.     ' store the item into the value() array
  218.     value(NewIndex).HashCode = hCode
  219.     value(NewIndex).HashIndex = ndx
  220.     value(NewIndex).Key = strKey
  221.     
  222. Add_SetItem:
  223. #If ValueType = 0 Then
  224.     ' objects and non-object values needs a different action
  225.     If IsObject(Item) Then
  226.         Set value(NewIndex).Item = Item
  227.     Else
  228.         value(NewIndex).Item = Item
  229.     End If
  230. #ElseIf ValueType = 1 Then
  231.     value(NewIndex).Item = Item
  232. #Else
  233.     Set value(NewIndex).Item = Item
  234. #End If
  235.  
  236.     ' clear the private collection
  237.     Set m_values = Nothing
  238.  
  239. End Sub
  240.  
  241. ' return an item
  242.  
  243. Function Item(Index As Variant) As Variant
  244. Attribute Item.VB_Description = "Return the item associated with this key or numeric index"
  245. Attribute Item.VB_UserMemId = 0
  246.     Dim ndx As Long
  247.     
  248.     If VarType(Index) = vbString Then
  249.         ' find the item given its key
  250.         ndx = hashTable(GetIndex((Index), , True)) ' pass by value
  251.     Else
  252.         ' find an item given its numeric Index
  253.         ndx = Index
  254.         CheckRange ndx
  255.     End If
  256.     
  257. #If ValueType = 0 Then
  258.     ' objects and non-object values needs a different action
  259.     If IsObject(value(ndx).Item) Then
  260.         Set Item = value(ndx).Item
  261.     Else
  262.         Item = value(ndx).Item
  263.     End If
  264. #ElseIf ValueType = 1 Then
  265.     Item = value(ndx).Item
  266. #Else
  267.     Set Item = value(ndx).Item
  268. #End If
  269. End Function
  270.  
  271. ' remove an item, given its alphabetical or numerical key
  272. ' if IgnoreIfNotFound=True, no error is raised if the item is not found
  273.  
  274. Sub Remove(Index As Variant, Optional IgnoreIfNotFound As Boolean)
  275. Attribute Remove.VB_Description = "Remove an item from the collection"
  276.     Dim ndx As Long, i As Long
  277.     Dim valueNdx As Long
  278.     
  279.     If VarType(Index) = vbString Then
  280.         ' remove an item given its key
  281.         ndx = GetIndex((Index)) ' pass by value
  282.         If ndx < 0 Then
  283.             If IgnoreIfNotFound Then Exit Sub
  284.             Err.Raise 5
  285.         End If
  286.         valueNdx = hashTable(ndx)
  287.     Else
  288.         ' remove an item given its numeric key
  289.         valueNdx = Index
  290.         If valueNdx < 1 Or valueNdx > m_Count Then
  291.             If IgnoreIfNotFound Then Exit Sub
  292.             Err.Raise 9
  293.         End If
  294.         ndx = value(valueNdx).HashIndex
  295.     End If
  296.     
  297.     ' clear this item
  298.     value(valueNdx) = value(0)
  299.     ' remove it from the value() array, shifting following items
  300.     If valueNdx < m_Count Then
  301.         CopyMemory ByVal VarPtr(value(valueNdx)), ByVal VarPtr(value(valueNdx + 1)), (m_Count - valueNdx) * itemLen
  302.         ZeroMemory ByVal VarPtr(value(m_Count)), itemLen
  303.     End If
  304.     ' decrease the counter
  305.     m_Count = m_Count - 1
  306.     ' adjust the Index backpointer for all subsequent items
  307.     For i = valueNdx To m_Count
  308.         hashTable(value(i).HashIndex) = i
  309.     Next
  310.     
  311.     ' remove this item from the hash table
  312.     hashTable(ndx) = 0
  313.     ' move all subsequent items in the correct position
  314.     i = ndx
  315.     Do
  316.         i = i + 1
  317.         If i > hashTableSize Then i = 1
  318.         ' get the corresponding index in the value() array
  319.         valueNdx = hashTable(i)
  320.         ' exit if element is blank
  321.         If valueNdx = 0 Then Exit Do
  322.         ' zero the HashCode value for this item
  323.         hashTable(i) = 0
  324.         ' search for the right index for this item
  325.         ndx = -GetIndex(value(valueNdx).Key, value(valueNdx).HashCode)
  326.         Debug.Assert ndx > 0
  327.         ' move the backpointer where it should go
  328.         hashTable(ndx) = valueNdx
  329.         value(valueNdx).HashIndex = ndx
  330.     Loop
  331.         
  332.     If m_Count = 0 Then mHighKey = "0"
  333.     ' clear the private collection
  334.     Set m_values = Nothing
  335.  
  336. End Sub
  337.  
  338. ' add support for enumeration (For Each ... Next)
  339. ' NOTE: this is a time consuming operation, and should be avoided if possible
  340. ' iterating using a regular For...Next loop is always *much* faster
  341.  
  342. Function NewEnum() As IUnknown
  343. Attribute NewEnum.VB_UserMemId = -4
  344. Attribute NewEnum.VB_MemberFlags = "40"
  345.     If (m_values Is Nothing) Then
  346.         ' build the collection on the fly
  347.         Dim i As Long
  348.         Set m_values = New Collection
  349.         For i = 1 To m_Count
  350.             m_values.Add value(i).Item, value(i).Key
  351.         Next
  352.     End If
  353.     Set NewEnum = m_values.[_NewEnum]
  354. End Function
  355.  
  356. ' return True if an item actually exists
  357.  
  358. Function Exists(Key As Variant) As Boolean
  359. Attribute Exists.VB_Description = "Return True if an item with this key exists in the collection"
  360.     If VarType(Key) = vbString Then
  361.         ' check the index corresponding to a given key
  362.         Exists = (GetIndex((Key)) > 0)    ' pass by value
  363.     Else
  364.         ' simply check that index is in range
  365.         Exists = (Key >= 1 And Key <= m_Count)
  366.     End If
  367. End Function
  368.  
  369. ' return the numerical index given the alphabetical key
  370. '   -1 if the item does not exist
  371.  
  372. Function Index(Key As String) As Long
  373. Attribute Index.VB_Description = "Return the numeric index of the item with this key, or -1 if the key is not found"
  374.     Dim ndx As Long
  375.     ndx = GetIndex((Key))  ' pass by value
  376.     If ndx > 0 Then
  377.         Index = hashTable(ndx)
  378.     Else
  379.         Index = -1
  380.     End If
  381. End Function
  382.  
  383. ' return the Key corresponding to a numerical index
  384. ' raises an error if index is out-of-range
  385. ' may be a null string, in which case no key was specified in the Add method
  386.  
  387. Function Key(Index As Long) As String
  388. Attribute Key.VB_Description = "Return the key of the item found in this position"
  389.     CheckRange Index
  390.     Key = value(Index).Key
  391. End Function
  392.  
  393. ' the sorted state of the collection
  394. ' (set to True to sort items)
  395.  
  396. Property Get Sorted() As Boolean
  397. Attribute Sorted.VB_Description = "Return or sets the sort status of the collection"
  398.     Sorted = m_Sorted
  399. End Property
  400.  
  401. Property Let Sorted(ByVal NewValue As Boolean)
  402.     If NewValue <> m_Sorted Then
  403.         m_Sorted = NewValue
  404.         If m_Sorted Then ShellSort
  405.     End If
  406. End Property
  407.  
  408. ' raise an error if an index is out of range
  409.  
  410. Private Sub CheckRange(ByVal numKey As Long)
  411.     'If numKey < 1 Or numKey > m_Count Then Err.Raise 9
  412. End Sub
  413.  
  414. ' binary search of a key
  415. ' assumes that key is lowercase, and that value() is sorted
  416. ' if found, returns the index of the item
  417. ' otherwise, returns the negated index of where it should be stored
  418.  
  419. Private Function BinarySearch(Key As String) As Long
  420.     Dim first As Long, last As Long, middle As Long
  421.     first = 1
  422.     last = m_Count
  423.     Do Until first > last
  424.         middle = (first + last) \ 2
  425.         Select Case StrComp(value(middle).Key, Key)
  426.             Case -1
  427.                 first = middle + 1
  428.             Case 1
  429.                 last = middle - 1
  430.             Case 0
  431.                 BinarySearch = middle
  432.                 Exit Function
  433.         End Select
  434.     Loop
  435.     BinarySearch = -first
  436. End Function
  437.  
  438. ' return the hash code of a string - TEXT should be lowercase
  439. ' (can't be a null value)
  440.  
  441. Private Function HashCode(text As String) As Long
  442.     Dim strLen As Long, result As Long, i As Long
  443.     ' allocate a static buffer (dramatically reduces overhead)
  444.     Static buffer(1 To 256) As Integer
  445.     
  446.     ' copy the string into an array of Longs (max 256 chars)
  447.     strLen = Len(text)
  448.     If strLen > 256 Then strLen = 256
  449.     CopyMemory ByVal VarPtr(buffer(1)), ByVal StrPtr(text), strLen * 2
  450.     
  451.     ' create the Hash code by adding all values
  452.     ' add a fixed value to account for null strings
  453.     result = buffer(1) + 17
  454.     For i = 2 To strLen
  455.         result = (result * 2 + buffer(i)) And &H3FFFFFFF
  456.     Next
  457.     HashCode = result
  458. End Function
  459.  
  460. ' return the position in value() of a given key and its Hash code
  461. ' if the item is not found, return the negated index of where it should go
  462. ' KEY is modified to its lowercase version
  463. ' If RaiseError = True, it raises an error if the item is not found
  464.  
  465. Private Function GetIndex(Key As String, Optional hCode As Long, Optional raiseError As Boolean) As Long
  466.     Dim ndx As Long, valueNdx As Long
  467.     
  468.     If hCode = 0 Then
  469.         ' if hash code is null, evaluate it
  470.         Key = LCase$(Key)
  471.         hCode = HashCode(Key)
  472.     End If
  473.     
  474.     ndx = (hCode Mod hashTableSize) + 1
  475.     Do
  476.         ' first, compare hash codes
  477.         valueNdx = hashTable(ndx)
  478.         If valueNdx = 0 Then
  479.             ' this item doesn't exist - raise error if requrested
  480.             If raiseError Then Err.Raise 5
  481.             ' else exit, but return its would-be position
  482.             GetIndex = -ndx
  483.             Exit Function
  484.         ElseIf value(valueNdx).HashCode = hCode Then
  485.             ' actually compare strings only if hash codes match
  486.             If value(valueNdx).Key = Key Then
  487.                 GetIndex = ndx
  488.                 Exit Function
  489.             End If
  490.         End If
  491.         ndx = ndx + 1
  492.         If ndx > hashTableSize Then ndx = 1
  493.     Loop
  494. End Function
  495.  
  496. ' rehash all internal tables
  497.  
  498. Private Sub RehashTables(newSize As Long)
  499.     Dim i As Long, ndx As Long
  500.     
  501.     ' enlarge the value() array, preserving current values
  502.     valueSize = newSize
  503.     ReDim Preserve value(valueSize) As TValue
  504.     
  505.     ' create a larger hashtable
  506.     ' always use an odd value to increase performance of HashTable
  507.     hashTableSize = (valueSize * m_FillRatio) Or 1
  508.     ReDim hashTable(hashTableSize) As Long
  509.     
  510.     ' rebuild the hash table
  511.     For i = 1 To m_Count
  512.         ndx = -GetIndex(value(i).Key, value(i).HashCode)
  513.         value(i).HashIndex = ndx
  514.         hashTable(ndx) = i
  515.     Next
  516.     
  517. End Sub
  518.  
  519. ' sort the value() array
  520.  
  521. Private Sub ShellSort()
  522.     Dim i As Long, j As Long
  523.     Dim firstItem As Long
  524.     Dim distance As Long
  525.     Dim tmpValue As TValue
  526.  
  527.     ' account for optional arguments
  528.     firstItem = 1
  529.     ' find the best value for distance
  530.     Do
  531.         distance = distance * 3 + 1
  532.     Loop Until distance > m_Count
  533.     
  534.     Do
  535.         distance = distance \ 3
  536.         For i = distance + 1 To m_Count
  537.             If value(i - distance).Key > value(i).Key Then
  538.                 ' save a copy of the data
  539.                 j = i
  540.                 CopyMemory ByVal VarPtr(tmpValue), ByVal VarPtr(value(j)), itemLen
  541.                 Do
  542.                     CopyMemory ByVal VarPtr(value(j)), ByVal VarPtr(value(j - distance)), itemLen
  543.                     j = j - distance
  544.                     If j <= distance Then Exit Do
  545.                 Loop While (value(j - distance).Key > tmpValue.Key)
  546.                 ' move the data back in the array
  547.                 CopyMemory ByVal VarPtr(value(j)), ByVal VarPtr(tmpValue), itemLen
  548.             End If
  549.         Next
  550.     Loop Until distance <= 1
  551.     
  552.     ' zero local variable before VB has a chance to deallocate it
  553.     ZeroMemory ByVal VarPtr(tmpValue), itemLen
  554.     
  555.     ' fix the backpointers for all items in the hash table
  556.     For i = 1 To m_Count
  557.         hashTable(value(i).HashIndex) = i
  558.     Next
  559.     
  560. End Sub
  561.  
  562. Property Get HighKey() As Variant
  563.     HighKey = mHighKey
  564. End Property
  565.  
  566. Property Let HighKey(ByVal NewValue As Variant)
  567.   mHighKey = NewValue
  568. End Property
  569.  
  570.